﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Xml;
using ExamSharedClasses.Commands;
using ExamSharedClasses;

using System.Runtime.Serialization.Formatters.Binary;

namespace ExamServer {
    /// <summary>
    /// (k) Jeder eingeloggte Benutzer wird in einem ClientConnectionThread-Objekt gehalten. Ein Thread
    /// hört dauernd auf ankommende Objekte.
    /// </summary>
    public class ClientConnectionThread : Connection {
        private static int idMax; // Zählervariable

        private int id;
        public UserAddress UserAddress { get; set; }
        public bool IsCurrentlyWritingAnExam { get; set; }

        public ClientConnectionThread( TcpClient client, NetworkStream stream, BinaryFormatter formatter, Session session, UserAddress userAddress )
            : base() {
            this.id = ++idMax;
            this.UserAddress = userAddress;
            this.Client = client;
            this.Stream = stream;
            this.Formatter = formatter;
            this.Session = session;
        }

        public override void ConnectionThreadMethod() {
            while (Client != null) {
                ProcessReceivedObject(Receive());
            }
        }

        /// <summary>
        /// Das eigentliche Senden macht weiterhin die Basisklassenmethode, jedoch wird
        /// das übergebene Objekt, das im normalen Programmablauf eine Session oder nur
        /// ein Command ist, analysiert und ggf. vervollständigt. Bei einem übergebenem
        /// Command-Objekt wird automatisch ein Session-Objekt, das das Command kapselt.
        /// </summary>
        /// <param name="o">Session- oder auch Command-Objekt</param>
        public override void Send( object o ) {
            try {                                   // automatische Vorgänge:
                if (o is Command) {
                    Command c = (Command)o;
                    o = new Session(c);
                }

                if (o is Session) {
                    Session s = (Session)o;
                    s.SessionId = Session.SessionId;    //  -SessionId in Sessionobjekt eintragen
                    Command c = s.Command;
                    if (c.Source.Equals(""))        //  -Commando.Source-Property ausfüllen, wenn nicht schon geschehen...
                        c.Source = App.SOURCE;
                }
            } catch (Exception ex) {
                Display(ex);
            }

            try {
                Display("SENDE: " + o.ToString());
                base.Send(o);
            } catch (Exception ex) {
                Close();
                Display(ex);
            }
        }

        /// <summary>
        /// Die gleichnamige Basisklassenmethode wird nur um die Möglichkeit
        /// einer Log-Ausgabe erweitert.
        /// </summary>
        /// <returns></returns>
        public override object Receive() {
            try {
                object o = base.Receive();
                Display("EMPFANGEN: " + o.ToString());
                return o;
            } catch (Exception ex) {
                Display(ex);
                Close(); //ok?
                return null;
            }
        }

        /// <summary>
        /// Diese Methode ist nötig, um das empfangene zu interpretieren. Wenn eine Verbindung
        /// mit dieser Klasse besteht, ist davon auszugehen, daß der User korrekt eingeloggt ist,
        /// somit hier alle Funktionalitäten, die ein User erwartet, eingebaut werden müssen.
        /// </summary>
        /// <param name="o">nur Sessionobjekte werden verstanden</param>
        public void ProcessReceivedObject( object o ) {

            if (o is Session) {
                Session receivedSession = (Session)o;
                Command receivedCommand = receivedSession.Command;
                User user = receivedSession.User;
                Display("Sessionübereinstimmung: " + Session.Validate(receivedSession));

                if (receivedCommand is Message) {
                    Message message = (Message)receivedCommand;
                    if (message.MessageId == 200)
                        IsCurrentlyWritingAnExam = true;
                    if (message.MessageId == 201)
                        IsCurrentlyWritingAnExam = false;

                } else if (receivedCommand is Ping) {
                    if (receivedCommand.Source != App.SOURCE) {
                        Send(new Session(Session.SessionId, receivedCommand));
                    }

                /*} AUTOMATISIERT else if (receivedCommand is ViewClass) {
                    // dem Dozenten eine Liste mit User anzeigen, die zu seiner Klasse gehören
                    ViewClass cvc = new ViewClass();
                    Class c = App.DatabaseConnection.GetClass(UserAddress);
                    cvc.ClassWithUsers = App.DatabaseConnection.GetClassWithUsers(c.ClassId);
                    // dazu abfragen, ob sie gerade eingeloggt sind
                    foreach (User u in cvc.ClassWithUsers.UserList) {
                        bool b1 = false, b2 = false;
                        foreach (ClientConnectionThread cct in App.ClientConnectionThreadList.ClientList) {
                            if (cct.UserAddress.UserId == u.UserId) {
                                b1 = true;
                                if (cct.IsCurrentlyWritingAnExam) {
                                    b2 = true;
                                }
                            }
                        }
                        cvc.IsUserLoggedIn.Add(b1);
                        cvc.IsUserWriting.Add(b2);
                    }
                    Send(new Session(cvc));*/

                } else if (receivedCommand is ChangePassword) {
                    App.DatabaseConnection.SetPassword(((ChangePassword)receivedCommand).Password);


                } else if (receivedCommand is ViewExams) {
                    // dem User eine Liste mit den für seine Klasse schreibbaren Klausuren erstellen
                    ViewExams cve = (ViewExams)receivedCommand;
                    cve.ExamList = App.DatabaseConnection.GetExamList(App.DatabaseConnection.GetClass(UserAddress).ClassId);
                    cve.ExamList = App.DatabaseConnection.TryGetSpezializedExamList(cve.ExamList, UserAddress.UserId);
                    Send(new Session(cve));

                } else if (receivedCommand is ViewExam) {
                    ViewExam receivedComViewExam = ((ViewExam)receivedCommand);

                    if (receivedComViewExam.Exam is ExamUser) {
                        ExamUser eu = (ExamUser)receivedComViewExam.Exam;
                        // oder, es wird eine geschriebene angefordert 

                        if (!eu.StartDateTime.Equals(new DateTime())) {
                            // dann wird es wohl schon ausgefüllt sein
                            // verknüpfung von user und exam -> exam_user
                            // zuerst spezialisierung der antworten auf antworttypen
                            // dann, mit ermittelter answer_number eintragen der antworten in answer
                            App.DatabaseConnection.SetExamUser((ExamUser)receivedComViewExam.Exam);
                            foreach (Answer answer in ((ExamWithQuestions)receivedComViewExam.Exam).AnswerList) {
                                if (answer.HasEmptyAnswer())
                                    answer.SetDefaultAnswer();
                                App.DatabaseConnection.SetAnswer(answer);
                            }
                            Send(receivedComViewExam);
                        } else {
                            // hier wird eine ausgefüllte Klasur angefordert...
                            receivedComViewExam = App.DatabaseConnection.GetComViewExam(eu.ExamId, eu.UserDetails.UserId);
                            Send(new Session(receivedComViewExam));
                        }

                    } else if (receivedComViewExam.Exam is ExamWithQuestions) {
                        // oder eine Vorlage abgelegt werden

                    } else if (receivedComViewExam.Exam is Exam) {
                        // hier solll eine Klausur zusammengestellt werden
                        receivedComViewExam = App.DatabaseConnection.GetComViewExam(receivedComViewExam.Exam.ExamId);

                        Send(new Session(receivedComViewExam));
                    }

                } else if (receivedCommand is Logout) {
                    Logout receivedlogout = (Logout)receivedCommand;
                    Display("Logout von " + receivedlogout.Source + " empfangen.");
                    if (receivedlogout.Source != App.SOURCE) {
                        Send(receivedlogout);
                    }
                    Display("Logout: Thread wird gestoppt");
                    Close();


                } else
                    Display("Fehler: empfangenes Objekt wird nicht unterstützt!");

            } else
                Display("Fehler: empfangenes Objekt wird nicht unterstützt!");
        }

        public void Display( object messageObject ) {
            Display(messageObject, null);
        }
        public void Display( object messageObject, Exception ex ) {
            string messageString = "ConnectionThread " + id + ": " + messageObject.ToString();
            App.Display(messageString, ex);
        }

        override public void Close() {
            App.ClientConnectionThreadList.Remove(this);
            base.Close();
        }
    }
}